﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Patch.cs" company="SmartPlan">
//   SmartPlan 2011
// </copyright>
// <summary>
//   The diff. http://razor.occams.info/code/diff/diff/html/index.html
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Algorithm.Diff
{
    using System;
    using System.Collections;
    using System.IO;
    using System.Text;
    using System.Xml;

    /// <summary>
    /// The patch.
    /// </summary>
    public class Patch : IEnumerable
    {
        /// <summary>
        /// The hunks.
        /// </summary>
        private readonly Diff.Hunk[] hunks;

        /// <summary>
        /// The left.
        /// </summary>
        private readonly IList left;

        /// <summary>
        /// The right.
        /// </summary>
        private readonly IList right;

        /// <summary>
        /// The right list data.
        /// </summary>
        private readonly object[] rightListData;

        /// <summary>
        /// Initializes a new instance of the <see cref="Patch"/> class.
        /// </summary>
        /// <param name="left">
        /// The left.
        /// </param>
        /// <param name="right">
        /// The right.
        /// </param>
        internal Patch(IList left, IList right)
        {
            this.left = null;
            this.right = null;
            this.left = left;
            this.right = right;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Patch"/> class.
        /// </summary>
        /// <param name="hunks">
        /// The hunks.
        /// </param>
        /// <param name="rightListData">
        /// The right list data.
        /// </param>
        internal Patch(Diff.Hunk[] hunks, object[] rightListData) : this(hunks, rightListData, null, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Patch"/> class.
        /// </summary>
        /// <param name="hunks">
        /// The hunks.
        /// </param>
        /// <param name="rightListData">
        /// The right list data.
        /// </param>
        /// <param name="left">
        /// The left.
        /// </param>
        /// <param name="right">
        /// The right.
        /// </param>
        internal Patch(Diff.Hunk[] hunks, object[] rightListData, IList left, IList right)
        {
            this.left = null;
            this.right = null;
            this.hunks = hunks;
            this.rightListData = rightListData;
            this.left = left;
            this.right = right;
        }

        /// <summary>
        /// The apply.
        /// </summary>
        /// <param name="patch">
        /// The patch.
        /// </param>
        /// <param name="left">
        /// The left.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public static Patch Apply(Patch patch, IList left)
        {
            if (patch.rightListData == null)
            {
                throw new ArgumentException("The patch is not a patch; it is a diff.");
            }

            ArrayList list = new ArrayList();
            ArrayList right = new ArrayList();
            int index = 0;
            foreach (Diff.Hunk hunk in (IEnumerable) patch)
            {
                list.Add(hunk);
                if (hunk.Same)
                {
                    for (int j = hunk.Left.Start; j <= hunk.Left.End; j++)
                    {
                        right.Add(left[j]);
                    }

                    continue;
                }

                for (int i = 0; i < hunk.Right.Count; i++)
                {
                    right.Add(patch.rightListData[index]);
                    index++;
                }
            }

            return new Patch((Algorithm.Diff.Diff.Hunk[]) list.ToArray(typeof(Algorithm.Diff.Diff.Hunk)), patch.rightListData, left, right);
        }

        /// <summary>
        /// The deserialize.
        /// </summary>
        /// <param name="input">
        /// The input.
        /// </param>
        /// <returns>
        /// </returns>
        public static Patch Deserialize(TextReader input)
        {
            return Deserialize(new XmlTextReader(input));
        }

        /// <summary>
        /// The deserialize.
        /// </summary>
        /// <param name="input">
        /// The input.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="FormatException">
        /// </exception>
        /// <exception cref="FormatException">
        /// </exception>
        /// <exception cref="FormatException">
        /// </exception>
        /// <exception cref="FormatException">
        /// </exception>
        public static Patch Deserialize(XmlReader input)
        {
            ArrayList list = new ArrayList();
            ArrayList list2 = new ArrayList();
            int num = 0;
            int num2 = 0;
            while (input.Read())
            {
                if (input.NodeType != XmlNodeType.Element)
                {
                    continue;
                }

                if (input.Name != "Patch")
                {
                    throw new FormatException("Data is not in the expected format.");
                }

                while (input.Read())
                {
                    if (input.NodeType == XmlNodeType.EndElement)
                    {
                        break;
                    }

                    if (input.NodeType == XmlNodeType.Element)
                    {
                        int num3 = int.Parse(input.GetAttribute("Count"));
                        string name = input.Name;
                        if (name != null)
                        {
                            name = string.IsInterned(name);
                            if (name == "Same")
                            {
                                list.Add(new Diff.Hunk(null, null, num, (num + num3) - 1, num2, (num2 + num3) - 1, true));
                                num += num3;
                                num2 += num3;
                                continue;
                            }

                            if (name == "Deletion")
                            {
                                list.Add(new Diff.Hunk(null, null, num, (num + num3) - 1, num2, num2, false));
                                num += num3;
                                continue;
                            }

                            if ((name == "Insertion") || (name == "Replacement"))
                            {
                                if ((input.Name == "Insertion") && (num3 != 0))
                                {
                                    throw new FormatException("Data is not in the expected format.");
                                }

                                int num4 = 0;
                                while (input.Read())
                                {
                                    if (input.NodeType == XmlNodeType.EndElement)
                                    {
                                        break;
                                    }

                                    if (input.NodeType == XmlNodeType.Element)
                                    {
                                        if (input.Name != "Line")
                                        {
                                            throw new FormatException("Data is not in the expected format.");
                                        }

                                        string str = input.ReadString();
                                        list2.Add(str);
                                        num4++;
                                    }
                                }

                                list.Add(new Diff.Hunk(null, null, num, (num + num3) - 1, num2, (num2 + num4) - 1, false));
                                num += num3;
                                num2 += num4;
                                continue;
                            }
                        }

                        throw new FormatException("Data is not in the expected format.");
                    }
                }

                break;
            }

            return new Patch((Algorithm.Diff.Diff.Hunk[]) list.ToArray(typeof(Algorithm.Diff.Diff.Hunk)), (object[]) list2.ToArray(typeof(object)));
        }

        /// <summary>
        /// The get right line.
        /// </summary>
        /// <param name="dataindex">
        /// The dataindex.
        /// </param>
        /// <param name="rightindex">
        /// The rightindex.
        /// </param>
        /// <returns>
        /// The get right line.
        /// </returns>
        private object GetRightLine(ref int dataindex, int rightindex)
        {
            if (this.rightListData != null)
            {
                return this.rightListData[dataindex++];
            }

            return this.Right[rightindex];
        }

        /// <summary>
        /// The serialize.
        /// </summary>
        /// <param name="output">
        /// The output.
        /// </param>
        public void Serialize(TextWriter output)
        {
            XmlTextWriter writer = new XmlTextWriter(output);
            writer.Formatting = Formatting.Indented;
            writer.Indentation = 1;
            writer.IndentChar = '\t';
            this.Serialize(writer);
        }

        /// <summary>
        /// The serialize.
        /// </summary>
        /// <param name="output">
        /// The output.
        /// </param>
        public void Serialize(XmlWriter output)
        {
            output.WriteStartElement("Patch");
            int dataindex = 0;
            foreach (Diff.Hunk hunk in this.hunks)
            {
                if (hunk.Same)
                {
                    output.WriteStartElement("Same");
                }
                else if (hunk.Left.Count == 0)
                {
                    output.WriteStartElement("Insertion");
                }
                else if (hunk.Right.Count == 0)
                {
                    output.WriteStartElement("Deletion");
                }
                else
                {
                    output.WriteStartElement("Replacement");
                }

                output.WriteAttributeString("Count", hunk.Left.Count.ToString());
                if (!hunk.Same)
                {
                    for (int i = 0; i < hunk.Right.Count; i++)
                    {
                        output.WriteElementString("Line", this.GetRightLine(ref dataindex, hunk.Right.Start + i).ToString());
                    }
                }

                output.WriteEndElement();
            }

            output.WriteEndElement();
        }

        /// <summary>
        /// The get enumerator.
        /// </summary>
        /// <returns>
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.hunks.GetEnumerator();
        }

        /// <summary>
        /// The to string.
        /// </summary>
        /// <returns>
        /// The to string.
        /// </returns>
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            int index = 0;
            foreach (Diff.Hunk hunk in this.hunks)
            {
                if (!hunk.Same)
                {
                    if (hunk.Left.Count == 0)
                    {
                        builder.Append("Insertion at line " + hunk.Left.Start + "\n");
                    }
                    else if (hunk.Right.Count == 0)
                    {
                        builder.Append(string.Concat(new object[] { "Deletion from line ", hunk.Left.Start, " to line ", hunk.Left.End, "\n" }));
                    }
                    else
                    {
                        builder.Append(string.Concat(new object[] { "Replacement from line ", hunk.Left.Start, " to line ", hunk.Left.End, "\n" }));
                    }

                    for (int i = 0; i < hunk.Right.Count; i++)
                    {
                        builder.Append("+");
                        builder.Append(this.rightListData[index]);
                        builder.Append("\n");
                        index++;
                    }
                }
            }

            return builder.ToString();
        }

        /// <summary>
        /// The write block.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="prefix">
        /// The prefix.
        /// </param>
        /// <param name="items">
        /// The items.
        /// </param>
        private static void WriteBlock(TextWriter writer, char prefix, Range items)
        {
            if ((items.Count > 0) && (items[0] is char))
            {
                WriteCharBlock(writer, prefix, items);
            }
            else
            {
                WriteStringBlock(writer, prefix, items);
            }
        }

        /// <summary>
        /// The write char block.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="prefix">
        /// The prefix.
        /// </param>
        /// <param name="items">
        /// The items.
        /// </param>
        private static void WriteCharBlock(TextWriter writer, char prefix, Range items)
        {
            bool flag = true;
            int num = 0;
            foreach (char ch in (IEnumerable) items)
            {
                if ((ch == '\n') && !flag)
                {
                    writer.WriteLine();
                    flag = true;
                }

                if (flag)
                {
                    writer.Write(prefix);
                    flag = false;
                    num = 0;
                }

                if (ch == '\n')
                {
                    writer.WriteLine("[newline]");
                    flag = true;
                }
                else
                {
                    writer.Write(ch);
                    num++;
                    if (num == 60)
                    {
                        writer.WriteLine();
                        flag = true;
                    }
                }
            }

            if (!flag)
            {
                writer.WriteLine();
            }
        }

        /// <summary>
        /// The write string block.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="prefix">
        /// The prefix.
        /// </param>
        /// <param name="items">
        /// The items.
        /// </param>
        private static void WriteStringBlock(TextWriter writer, char prefix, Range items)
        {
            foreach (object obj2 in (IEnumerable) items)
            {
                writer.Write(prefix);
                writer.WriteLine(obj2.ToString());
            }
        }

        /// <summary>
        /// The write unified diff.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        public void WriteUnifiedDiff(TextWriter writer)
        {
            this.WriteUnifiedDiff(writer, "Left", "Right", 2);
        }

        /// <summary>
        /// The write unified diff.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="fromfile">
        /// The fromfile.
        /// </param>
        /// <param name="tofile">
        /// The tofile.
        /// </param>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// </exception>
        public void WriteUnifiedDiff(TextWriter writer, string fromfile, string tofile, int context)
        {
            if ((this.left == null) || (this.right == null))
            {
                throw new InvalidOperationException("Unified diffs can only be created from patches loaded with full content data.");
            }

            writer.Write("--- ");
            writer.WriteLine(fromfile);
            writer.Write("+++ ");
            writer.WriteLine(tofile);
            ArrayList hunks = new ArrayList();
            foreach (Diff.Hunk hunk in (IEnumerable) this)
            {
                Diff.Hunk hunk2 = null;
                if (hunks.Count > 0)
                {
                    hunk2 = (Algorithm.Diff.Diff.Hunk) hunks[hunks.Count - 1];
                }

                if (hunk.Same)
                {
                    if (hunk2 == null)
                    {
                        if (hunk.Left.Count > context)
                        {
                            hunks.Add(hunk.Crop(hunk.Left.Count - context, 0));
                        }
                        else
                        {
                            hunks.Add(hunk);
                        }
                    }
                    else if (hunk.Left.Count <= (context * 2))
                    {
                        hunks.Add(hunk);
                    }
                    else
                    {
                        hunks.Add(hunk.Crop(0, hunk.Left.Count - context));
                        WriteUnifiedDiffSection(writer, hunks);
                        hunks.Clear();
                        if (hunk.Left.Count > context)
                        {
                            hunks.Add(hunk.Crop(hunk.Left.Count - context, 0));
                        }
                        else
                        {
                            hunks.Add(hunk);
                        }
                    }

                    continue;
                }

                hunks.Add(hunk);
            }

            if ((hunks.Count > 0) && ((hunks.Count != 1) || !((Algorithm.Diff.Diff.Hunk) hunks[0]).Same))
            {
                WriteUnifiedDiffSection(writer, hunks);
            }
        }

        /// <summary>
        /// The write unified diff section.
        /// </summary>
        /// <param name="writer">
        /// The writer.
        /// </param>
        /// <param name="hunks">
        /// The hunks.
        /// </param>
        private static void WriteUnifiedDiffSection(TextWriter writer, ArrayList hunks)
        {
            Diff.Hunk hunk = (Algorithm.Diff.Diff.Hunk) hunks[0];
            Diff.Hunk hunk2 = (Algorithm.Diff.Diff.Hunk) hunks[hunks.Count - 1];
            writer.Write("@@ -");
            writer.Write(hunk.Left.Start);
            writer.Write(",");
            writer.Write(hunk2.Left.End);
            writer.Write(" +");
            writer.Write(hunk.Right.Start);
            writer.Write(",");
            writer.Write(hunk2.Right.End);
            writer.WriteLine(" @@");
            foreach (Diff.Hunk hunk3 in hunks)
            {
                if (hunk3.Same)
                {
                    WriteBlock(writer, ' ', hunk3.Left);
                }
                else
                {
                    WriteBlock(writer, '-', hunk3.Left);
                    WriteBlock(writer, '+', hunk3.Right);
                }
            }
        }

        /// <summary>
        /// Gets Left.
        /// </summary>
        public IList Left
        {
            get
            {
                return this.left;
            }
        }

        /// <summary>
        /// Gets Right.
        /// </summary>
        public IList Right
        {
            get
            {
                return this.right;
            }
        }
    }
}

